跳到主要内容

TCP 的报文基本结构

概述

TCP/IP 协议实际是指一个 4 层的协议系统,也称为 TCP/IP 协议族

image.png

这里 TCP/IP 协议分层如图,所以不要把 TCP/IP 协议族 和 TCP 协议弄混 image.png

TCP 是 TCP/IP 体系中面向连接的运输层协议,它提供全双工的和可靠交付的服务。TCP与UDP最大的区别就是:TCP是面向连接的,而UDP是无连接的。TCP比UDP要复杂得多,除了具有面向连接和可靠传输的特性外,TCP还在运输层使用了流量控制和拥塞控制机制。

TCP(Transmission Control Protocol)提供一种面向连接的、可靠的字节流服务。面向连接即表示两个应用(一般是一个客户端和一个服务端)在彼此交换数据前先建立一个 TCP 连接,同时 TCP 通过差错与流量控制确保在不可靠的网络中完成数据的正确传输

TCP 连接有三个阶段,即 连接建立数据传送连接释放。 注意:TCP 是传输层协议,只是建立在通信的两个端系统中,而完全不关路由器啥事(路由器处理的是网络层的东西)

TCP 的主要特点

TCP是面向连接的运输层协议

应用程序在使用TCP提供的服务传送数据之前,必须先建立TCP连接。建立连接的目的是通信双方为接下来的数据传送做好准备,初始化各种状态变量,分配缓存等资源。在传送数据完毕后,必须释放已建立的TCP连接,即释放相应的资源和变量。这个过程与打电话类似:通话前要先拨号建立连接,通话结束后要挂机释放连接。

每一条TCP连接只能是点对点的(一对一)

TCP连接唯一地被通信两端的端点所确定,而两个端点分别由二元组(IP地址、端口号)唯一标识,即一条TCP连接由两个套接字(socket)地址标识。

与 UDP 的端口队列不同的是,TCP 的发送缓存和接收缓存都是分配给一个连接的,而不是一个端口。

TCP的一个连接由四元组(源IP地址、源端口号、目的IP地址、目的端口号)标识,即由源/目的套接字(socket)地址对标识。也就是说,来自不同源的TCP报文段,即使它们的目的IP地址和目的端口号相同,它们也不可能被交付到同一个TCP接收缓存中,因为它们在不同的TCP“管道”中传输,到达不同“管道”出口的缓存。

通常一个TCP服务器进程用一个端口号与不同的客户机进程建立多个连接,然后创建多个子进程分别用这些连接与各自的客户机进程进行通信。

TCP提供可靠交付的服务

通过TCP连接传送的数据无差错、不丢失、不重复,并且按序到达。

TCP提供全双工通信

TCP允许通信双方的应用进程在任何时候都能发送数据。TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。在发送时,应用程序在把数据传送给TCP的缓存后,就可以做自己的事,而TCP在合适的时候把数据发送出去。在接收时,TCP把收到的数据放入缓存,上层的应用进程在合适的时候读取缓存中的数据。

面向字节流

TCP中的“流”(stream)指的是流入到进程或从进程流出的字节序列。“面向字节流”的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成是一连串的无结构的字节流。TCP不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系。但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样。

例如:发送方应用程序交给发送方的TCP共10个数据块,而接收方的应用程序是分4次(即4个数据块)从TCP接收方缓存中将数据读取完毕。

注意:图5-7中的TCP连接是一条虚连接,而不是一条物理连接。也就是说,TCP连接是一种抽象的逻辑连接。TCP报文段首先要传送到IP层,加上IP首部后,再传送到数据链路层,再加上数据链路层的首部和尾部后,才离开主机发送到物理链路。

另外,TCP连接仅存在于两个端系统中,而网络核心的中间设备(路由器、交换机等)完全不知道该连接的存在。TCP连接的组成主要包括:通信两端主机上的缓存、状态变量,在这两台主机间的路由器和交换机没有为该连接分配任何缓存和变量。

image.png

  • 发送方 的应用进程按照自己产生数据的规律,不断地把数据块(其长短可能各异)陆续写入到TCP的发送缓存中。TCP再从发送缓存中取出一定数量的数据,将其组成TCP报文段(segment)逐个传送给IP层,然后发送出去。(图中表示的是在TCP连接上传送一个个TCP报文段而没有画出IP层或链路层的动作)

  • 接收方 从IP层收到TCP报文段后,先把它暂存在接收缓存中,然后等待接收方的应用进程从接收缓存中将数据按顺序读取。

需要注意的是:接收方应用进程每次从接收缓存中读取数据时,是按应用进程指定的数量读取数据,而不是一次读取接收缓存中的一个完整的报文段或所有数据。(只有当接收缓存中的数据量小于应用进程指定的读取量时,才返回给应用进程接收缓冲中所有的数据)当接收缓存中完全没有数据时,根据读取方式的不同,应用进程可能会一直等待,也可能直接返回。

由此可见,TCP的接收方应用进程读取的数据块的边界与发送方应用进程发送的数据块边界毫无关系,也就是说TCP接收方在向上层交付数据时不保证能保持发送方应用进程发送数据块的边界。

TCP 报文结构

TCP虽然是面向字节流的,但TCP传送的数据单元却是报文段。TCP报文段分为首部和数据两部分,而TCP的全部功能都体现在它首部中各字段的作用。因此,只有弄清TCP首部各字段的作用才能掌握 TCP的工作原理。

image.png

TCP 报文段首部的前20个字节是固定的,后面有 4N 字节是根据需要而增加的选项(N必须是整数)。因此 TCP 首部的最小长度是20字节。

固定报文字段

源端口和目的端口 各占 2 个字节

与 UDP 一样,该字段定义了在主机中发送和接收该报文段的应用程序的端口号,用于传输层的复用和分用。

序号 占 4 字节(一字节等于8位)用小写的 seq

序号从 0 开始,到 23212^{32} – 1 为止,共 2322^{32}(即 4294967296)个序号。TCP是面向数据流的。TCP传送的报文段可看为连续的数据流。在一个TCP连接中传送的数据流中的每一个字节都按顺序编号。整个数据的起始序号在连接建立时设置。首部中的序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。

例如:报文段的序号字段值是 301,而携带的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。显然,下一个报文段的数据序号应当从401开始,因而下一个报文段的序号字段值应为401。

确认号 占 4 字节(用小写的 ack 来区分 ACK标志位)

确认号是期望收到对方的下一个报文段的第一个数据字节的序号。 TCP 提供的是双向通信,当一端发送数据时同时对接收到的对端数据进行确认。

例如:B 正确收到了 A 发送过来的一个报文段,其序号字段值是501,而数据长度是200字节,这表明 B 正确收到了 A 发送的序号在501~700 的数据。因此,B 期望收到 A 的下一个数据序号是701,于是 B 在发送给 A 的 确认报 文段中把确认号置为701,表示对第701字节之前(不包括第701字节)的所有字节的确认。TCP 采用的是累积确认。由于序号字段有 32 位长,可对 4 GB(即 4 千兆字节)的数据进行编号。这样就可保证在大多数情况下当序号重复使用时,旧序号的数据早已通过网络到达终点了。

注意:这个是接收方发给传送方的

数据偏移 占 4 位

数据偏移,它指出 TCP 报文段的数据起始处距离 TCP报文段的起始处有多远。这实际上就是TCP报文段首部的长度。由于首部长度不固定(因首部中还有长度不确定的选项字段),因此数据偏移字段是必要的。但应注意,“数据偏移”的单位不是字节而是32位(即以4字节长的字为计算单位)。由于4位二进制数能够表示的最大十进制数字是15,因此数据偏移的最大值是60字节(4 * 15),这也是TCP首部的最大长度。

保留 占 6 位

保留为今后使用,但目前应置为 0。

窗口 占 2 字节

窗口值指示发送该报文段一方的接收窗口大小,在 0 到 21612^{16} – 1 之间。窗口字段用来控制对方发送的数据量(从确认号开始,允许对方发送的数据量),单位为字节。窗口字段反映了接收方接收缓存的可用空间大小,计算机网络经常用接收方的接收能力的大小来控制发送方的数据发送量。

例如:设确认号是 701,窗口字段是 1000。这表明,允许对方发送数据的序号范围为701~1700。

检验和 占 2 字节

检验和字段检验的范围包括首部和数据这两部分。和 UDP 用户数据报一样,在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。(这部分和 UDP 的是一样的,所以直接照搬 UDP 的校验方式)

所谓 “伪首部” 是因为这种伪首部并不是 UDP 用户数据报真正的首部。只是在计算检验和时,临时和 UDP 用户数据报连接在一起,得到一个临时的 UDP 用户数据报。检验和就是按照这个临时的 UDP 用户数据报来计算的。伪首部既不向下传送也不向上递交,而仅仅是为了计算检验和,防止报文被意外地交付到错误的目的地。

UDP 计算检验和的方法和计算 IP 数据报首部检验和的方法相似。但不同的是:IP 数据报的检验和只检验 IP 数据报的首部,但 UDP 的检验和是把首部和数据部分一起都检验。在发送方,首先是先把全零放入检验和字段。再把伪首部及 UDP 用户数据报看成是由许多16位的字串接起来。若 UDP 用户数据报的数据部分不是偶数个字节,则要填入一个全零字节(但此字节不发送)。然后按二进制反码计算出这些16位字的和。将此和的二进制反码写入检验和字段后,发送这样的 UDP 用户数据报。在接收方,把收到的 UDP 用户数据报连同伪首部(及可能的填充全零字节)一起,按二进制反码求这些16位字的和。当无差错时其结果应为全1,否则就表明有差错出现,接收方就应丢弃这个 UDP 用户数据报(也可以上交给应用层,但附上出现了差错的警告)。这种简单的差错检验方法的检错能力并不强,但它的好处是简单,处理起来较快。伪首部的第3字段是全零,第4个字段是 IP 首部中的协议字段的值。以前已讲过,对于 UDP,此协议字段值为17。第5字段是 UDP 用户数据报的长度。这样的检验和,既检查了 UDP 用户数据报的源端口号、目的端口号及 UDP 用户数据报的数据部分,又检查了 IP 数据报的源 IP 地址和目的地址。

TCP 伪首部的格式与图5-5中 UDP 用户数据报的伪首部一样。但应将伪首部第4个字段中的17改为6(TCP的协议号是6),将第5字段中的UDP长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。

image.png

6 个标志位

紧急 URG (URGent)

当 URG=1 时,表明紧急指针字段有效。它告诉接收方 TCP此报文段中有紧急数据,应尽快交付给应用程序(相当于高优先级的数据),而不要按序从接收缓存中读取。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行。因此用户从键盘发出 中断命令(Control+C)。如果不使用紧急数据,那么这两个字符将存储在接收TCP缓存的末尾。只有在所有的数据被处理完毕后这两个字符才被交付到接收应用进程。这样做就浪费了许多时间。当URG置1时,发送应用进程就告诉发送TCP这两个字符是紧急数据。于是发送TCP就将这两个字符插入到报文段的数据的最前面,其余的数据都是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。紧急指针指出在本报文段中的紧急数据共有多少个字节。紧急数据到达接收方后,当所有紧急数据都被处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为零时也可发送紧急数据。URG在实际中很少被使用。

确认 ACK

只有当 ACK=1 时确认号字段才有效。当 ACK=0时,确认号无效。

推送 PSH(PUSH)

出于效率的考虑,TCP 可能会延迟发送数据或向应用程序延迟交付数据,这样可以一次处理更多的数据。但是当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,应用程序可以通知TCP使用推送(PUSH)操作。这时,发送方 TCP 把 PSH 置1,并立即创建一个报文段发送出去,而不需要积累到足够多的数据再发送。接收TCP收到PSH置1的报文段,就尽快地交付给接收应用进程,而不再等到接收到足够多的数据才向上交付。虽然应用程序可以选择推送操作,但现在多数TCP实现都是根据情况自动设置PUSH标志,而不是交由应用程序去处理。

复位 RST(ReSeT)

当 RST=1 时,表明 TCP 连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接。RST 置 1 还用来拒绝一个非法的报文段或拒绝打开一个连接。RST也可称为重建位或重置位。

同步 SYN

用来建立一个连接。当 SYN=1ACK=0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN=1ACK=1。因此,SYN 置为 1 就表示这是一个连接请求或连接接受报文。

终止 FIN(FINal)

用来释放一个连接。当 FIN=1 时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接。

可选部分

选项 长度可变

这里只介绍一种选项,即最大报文段长度(Maximum Segment Size,MSS)。

MSS 告诉对方 TCP:“我的缓存所能接收的报文段的数据字段的最大长度是MSS个字节。”

当没有使用选项时,TCP 的首部长度是20字节。MSS 的选择并不太简单。

若选择较小的 MSS 长度,网络的利用率就降低。设想在极端的情况下,当 TCP 报文段只含有1字节的数据时,在 IP 层传输的数据报的开销至少有40字节(包括 TCP 报文段的首部和 IP 数据报的首部)。这样,对网络的利用率就不会超过1/41。到了数据链路层还要加上一些开销。

但反过来,若 TCP 报文段非常长,那么在 IP 层传输时就有可能要分解成多个短数据报片。在目的站要将收到的各个短数据报片装配成原来的 TCP 报文段。当传输出错时还要进行重传。这些也都会使开销增大。一般认为,MSS 应尽可能大些,只要在 IP 层传输时不需要再分片就行。在连接建立的过程中,双方可以将自己能够支持的 MSS 写入这一字段。在以后的数据传送阶段,MSS 取双方提出的较小的那个数值。若主机未填写这项,则MSS的默认值是536 字节长。

因此,所有在因特网上的主机都应能接受的报文段长度是 536+20=556 字节。